#include "connection.h"
#include <arpa/inet.h>
#include <assert.h>
#include <chrono>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <functional>
#include <memory>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <system_error>
#include <unistd.h>
#include "channel.h"
#include "poll_poller.h"
#include "timer_event.h"
#include "trace.h"
void set_noblock(int sockfd)
{
    // non-block
    int flags = ::fcntl(sockfd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    int ret = ::fcntl(sockfd, F_SETFL, flags);
    // close-on-exec
    flags = ::fcntl(sockfd, F_GETFD, 0);
    flags |= FD_CLOEXEC;
    ret = ::fcntl(sockfd, F_SETFD, flags);
    (void)ret;
}

// static int init_connid = 0;
connection::connection(poll_poller* poller, struct sockaddr* server_addr_ptr)
    : channel(poller), server_addr_ptr_(server_addr_ptr)
{
    fd_ = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
    if (fd_ < 0) {
        throw std::system_error(std::error_code(errno, std::system_category()), "socket");
    }
    // set_noblock(fd_);
    TRACE_DEBUG("connection create fd:%d", fd_);
}

connection::~connection()
{
}

int getSocketError(int sockfd)
{
    int  optval = 0;
    auto optlen = static_cast<socklen_t>(sizeof optval);

    if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0) {
        return errno;
    }
    else {
        return optval;
    }
}

void connection::connect_server()
{
    auto      ret       = ::connect(fd_, server_addr_ptr_, sizeof(struct sockaddr_in));
    const int tmp_error = (ret == 0) ? 0 : errno;
    TRACE_DEBUG("connect_server fd:%d connect ret:%d errno:%d", fd_, ret, tmp_error);
    switch (tmp_error) {
        case 0:
        case EINPROGRESS:
        case EINTR:
        case EISCONN: {
            connecting();
        } break;
        default: {  // error continue
            TRACE_SYSTEM("connect error  try again fd:%d", fd_);
            retry_connect();
        } break;
    }
}

void connection::connecting()
{
    state_ = CONNECTING;
    poller_->add_channel(shared_from_this());
    set_write_callback(std::bind(&connection::handle_write, this));
    set_write_event();
}

std::string connection::get_addr() const
{
    std::string addr_pair;

    {
        struct sockaddr_in addr;
        socklen_t          len    = sizeof(struct sockaddr);
        auto               result = ::getsockname(fd_, reinterpret_cast<struct sockaddr*>(&addr), &len);
        if (result < 0) {
            TRACE_SYSTEM("getsockname error");
            return addr_pair;
        }

        char ipstr[INET_ADDRSTRLEN] = {0};
        auto ret                    = inet_ntop(AF_INET, &(addr.sin_addr), ipstr, INET_ADDRSTRLEN);
        if (ret == nullptr) {
            TRACE_SYSTEM("inet_ntop error");
        }
        else {
            addr_pair += std::string(ipstr) + ":" + std::to_string(ntohs(addr.sin_port)) + std::string("-->");
        }
    }

    {
        struct sockaddr_in addr;
        socklen_t          len    = sizeof(struct sockaddr);
        auto               result = ::getpeername(fd_, reinterpret_cast<struct sockaddr*>(&addr), &len);
        if (result < 0) {
            TRACE_SYSTEM("getpeername error");
            return addr_pair;
        }

        char ipstr[INET_ADDRSTRLEN] = {0};
        auto ret                    = inet_ntop(AF_INET, &(addr.sin_addr), ipstr, INET_ADDRSTRLEN);
        if (ret == nullptr) {
            TRACE_SYSTEM("inet_ntop error");
        }
        else {
            addr_pair += std::string(ipstr) + ":" + std::to_string(ntohs(addr.sin_port));
        }
    }
    return addr_pair;
}

void connection::retry_connect()  // 还没有 add channel
{
    set_no_write_event();
    poller_->remove_channel(shared_from_this());
    close(fd_);
    fd_ = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
    if (fd_ < 0) {
        throw std::system_error(std::error_code(errno, std::system_category()), "socket");
    }
    // set_noblock(fd_);
    timer_event te;
    te.func = std::bind(&connection::connect_server, this);
    te.trigger_time += retry_interval_;
    te.interval = 0;
    poller_->add_timer_event(te);
    TRACE_DEBUG("retry_connect fd:%d", fd_);
}

void connection::handle_read()
{
    if (state_ == CONNECTED) {
        auto n = ::read(fd_, readbuffer_, ONCE_READ_LENGTH);
        if (n > 0) {
            readbuffer_[n] = '\0';
            TRACE_DEBUG("handle_read fd:%d  readbuffer:%s", fd_, readbuffer_);
        }
        else if (n == 0) {
            handle_close();
        }
        else {
            handle_error();
        }

        TRACE_DEBUG("handle_read fd:%d", fd_);
    }
    else {
        // what happened?
        assert(state_ == DISCONNECTED);
    }
}

void connection::handle_write()
{
    if (state_ == CONNECTING) {
        int err = getSocketError(fd_);
        if (err) {
            TRACE_DEBUG("Connector::handleWrite - SO_ERROR =  fd:%d error:%s", fd_, strerror(err));
            retry_connect();
        }
        else {
            state_ = CONNECTED;
            set_read_event();
            set_read_callback(std::bind(&connection::handle_read, this));
            set_close_callback(std::bind(&connection::handle_read, this));
            set_error_callback(std::bind(&connection::handle_close, this));
            TRACE_DEBUG("connect success  fd:%d  addr:%s", fd_, get_addr().c_str());
        }
    }
    else {
        // what happened?
        assert(state_ == DISCONNECTED);
    }
}

void connection::handle_close()
{
    state_ = DISCONNECTED;
    TRACE_DEBUG("close fd_:%d", fd_);
    set_no_event();
    poller_->remove_channel(shared_from_this());
    close(fd_);
}

void connection::handle_error()
{
    handle_close();
}